mod alog;
mod backend;
mod config;
mod error;
mod http;
mod iface;
mod listener;
mod modules;
mod openssl;
mod runtime;
mod signal;
mod stream;
mod utils;

use crate::error::*;
use std::{
    fs,
    path::Path,
    process::exit,
    sync::{atomic::Ordering, Arc},
    time::Duration,
};

use alog::{
    file_output::{FileOutput, FileOutputOptions, Rolling},
    use_alogger, Log,
};
use clap::{arg, command, Parser};
use config::{
    http::{
        main::{
            get_http_main_runtime_conf, set_http_main_runtime_conf,
            HttpMainConf,
        },
        site_conf::HttpSiteConf,
        site_mgr::HttpSiteRuntimeConfManager,
    },
    linker::{get_linker_conf, set_linker_conf, LinkerConf},
    listeners::{get_listeners_conf, set_listeners_conf, ListenersConf},
};
use http::{
    pool::{client_pool::ClientConnPools, server_pool::ServerConnPool},
    HttpServerApp,
};
use iface::AppManager;
use listener::ListenerManager;
use log::*;
use modules::ModuleMgr;
use runtime::{RuntimeManager, PROCESS_EXIT, SHUTDOWN_TOKEN, WAIT_TASKS};
use signal::SignalManager;

#[derive(Parser, Debug, Default)]
#[command(author, version, about)]
struct Args {
    /// The config file path
    #[arg(short, long, required = false)]
    conf: String,
}

fn error_log_init() -> Result<()> {
    let linker_conf = get_linker_conf();
    let err_log_conf = &linker_conf.error_log;

    if !err_log_conf.enabled {
        return Ok(());
    }

    Log::set_console(err_log_conf.console);

    let level = match err_log_conf.level.as_str() {
        "debug" => alog::Level::Debug,

        "info" => alog::Level::Info,

        "warn" => alog::Level::Warn,

        "error" => alog::Level::Error,

        _ => alog::Level::Debug,
    };

    Log::set_level(level);

    let mut option = FileOutputOptions {
        buf_size: err_log_conf.buf_size,
        max_pending_bufs: err_log_conf.max_pending_bufs,
        flush_interval_ms: err_log_conf.flush_interval_ms,
        ..Default::default()
    };

    let rolling = match err_log_conf.rolling.as_str() {
        "minute" => Rolling::Minute,

        "hour" => Rolling::Hour,

        "day" => Rolling::Day,

        _ => Rolling::None,
    };

    option.rolling = rolling;

    option = option.valid();

    let file_output =
        FileOutput::new(Some(option), err_log_conf.file_path.clone())?;
    Log::set_output(Box::new(file_output));

    Ok(())
}

fn load_listener_conf() -> Result<()> {
    let linker_conf = get_linker_conf();
    let conf = ListenersConf::load_from_yaml(&linker_conf.listeners_conf)?;
    set_listeners_conf(conf);

    Ok(())
}

fn load_http_site_conf(path: &Path) -> Result<()> {
    let path_str = path.to_string_lossy().to_string();

    let file_name = path
        .file_name()
        .if_none(
            ErrorType::InvalidError,
            format!("get http_site_conf file_name failed, path: {}", &path_str),
        )?
        .to_str()
        .if_none(
            ErrorType::InvalidError,
            format!(
                "http_site_conf file_name to str failed, path: {}",
                &path_str
            ),
        )?;

    let server_name = file_name.strip_suffix(".yaml").if_none(
        ErrorType::InvalidError,
        format!("get server_name failed, path: {}", &path_str),
    )?;

    let site_conf = HttpSiteConf::load_from_yaml(&path_str, server_name)?;

    HttpSiteRuntimeConfManager::add_site_conf(
        server_name.to_string(),
        site_conf,
    );

    Ok(())
}

fn load_http_conf() -> Result<()> {
    let linker_conf = get_linker_conf();
    let main_runtime_conf =
        HttpMainConf::load_from_yaml(&linker_conf.http_main_conf)?;
    set_http_main_runtime_conf(main_runtime_conf);

    let main_runtime_conf = get_http_main_runtime_conf();

    // load http sites conf.
    let entrys = fs::read_dir(Path::new(&main_runtime_conf.sites_enabled_path))
        .if_err(
            ErrorType::InvalidError,
            format!(
                "read http_sites_enabled dir failed, path: {}",
                &main_runtime_conf.sites_enabled_path
            ),
        )?;

    for entry in entrys {
        let entry = entry.if_err(
            ErrorType::InvalidError,
            format!(
                "read http_sites_enabled entry failed, path: {}",
                &main_runtime_conf.sites_enabled_path
            ),
        )?;

        let path = entry.path();
        let path_str = path.to_string_lossy().into_owned();
        if path.is_file() && path_str.ends_with(".yaml") {
            load_http_site_conf(&path)?;
        }
    }

    Ok(())
}

async fn quit_server() {
    let linker_conf = get_linker_conf();

    PROCESS_EXIT.store(true, Ordering::Relaxed);
    SHUTDOWN_TOKEN.cancel();

    ListenerManager::stop();

    _ = ModuleMgr::exit_process_handles().await;

    let mut wait_time_ms: usize = 0;

    loop {
        if WAIT_TASKS.load(Ordering::Relaxed) == 0 {
            break;
        }

        // TODO:
        // wait server_conn_nums == 0
        // wait client_conn_nums == 0

        tokio::time::sleep(Duration::from_millis(50)).await;
        wait_time_ms += 50;

        // force exit
        if wait_time_ms > linker_conf.graceful_shutdown_timeout_ms {
            error!("wait_time_ms: {}, graceful_shutdown_timeout_ms: {}, force exit", wait_time_ms,  linker_conf.graceful_shutdown_timeout_ms);
        }
    }

    // stop log
    Log::stop();

    exit(0);
}

async fn run_server() -> Result<()> {
    let mut signal_mgr = SignalManager::new();

    let http_main_runtime_conf = get_http_main_runtime_conf();

    // init http server session pool & client session pool.
    _ = ServerConnPool::init(http_main_runtime_conf.server_conn_pool_capcity);
    _ = ClientConnPools::init(
        http_main_runtime_conf.client_conn_pool_nums,
        http_main_runtime_conf.client_conn_pool_capacity,
        http_main_runtime_conf.client_conn_pool_one_pre_host,
    );

    // register app.
    AppManager::register_app(
        "http".to_string(),
        Arc::new(Box::new(HttpServerApp {})),
    );

    ModuleMgr::init_process_handles().await?;

    // register listeners & run listeners accept.
    let listeners_conf = get_listeners_conf();
    for l_c in &listeners_conf.listeners {
        let l_c_clone = Arc::new(l_c.clone());
        ListenerManager::register_listener_by_conf(l_c_clone)?;
    }
    ListenerManager::run();

    loop {
        tokio::time::sleep(Duration::from_millis(100)).await;

        signal_mgr.recv().await;
        if signal_mgr.terminate {
            exit(0);
        }

        if signal_mgr.quit {
            quit_server().await;
        }
    }
}

fn main() {
    // tokio_console
    // console_subscriber::init();
    Log::set_console(true);
    use_alogger();

    let args = Args::parse();
    debug!("args: {:?}", args);

    if args.conf.is_empty() || !args.conf.starts_with('/') {
        error!("The linker config must be an absolute path");
        exit(1);
    }

    match LinkerConf::load_from_yaml(&args.conf) {
        Ok(conf) => {
            set_linker_conf(conf);
        }
        Err(e) => {
            error!(
                "Load linker config failed, path: {}, err: {}",
                &args.conf, e
            );
            exit(1);
        }
    }

    if let Err(err) = error_log_init() {
        error!("error_log_init failed, err: {}", err);
        exit(1);
    }

    if let Err(err) = load_listener_conf() {
        error!("load_listener_conf failed, err: {}", err);
        exit(1);
    }

    if let Err(err) = load_http_conf() {
        error!("load_http_conf failed, err: {}", err);
        exit(1);
    }

    let linker_conf = get_linker_conf();

    RuntimeManager::init_main_runtime(linker_conf.main_threads);
    RuntimeManager::init_worker_runtime(
        linker_conf.worker_cpu_affinity.clone(),
        linker_conf.worker_threads,
    );

    RuntimeManager::main_runtime_block_on(async {
        if let Err(e) = run_server().await {
            error!("run_server failed, err: {}", e);
            Log::stop();
        }
    });
}
